Promises 可以幫助理解非同步操作的基本處理機制,這對於理解後續的 async/await
很重要,所以先從 promise 開始說起非同步。
特性 | 同步 | 非同步 |
---|---|---|
執行方式 | 依次執行 | 允許其他任務在等待操作時執行 |
程式碼順序 | 由上而下依序編譯 | 可以在任務完成後透過回呼、Promise 或 async/await 繼續執行 |
性能影響 | 可能導致長時間的阻塞,導致使用者過長時間等待畫面出現 | 提高反應性,避免長時間的阻塞 |
複雜性 | 通常較簡單,但是因為太簡單,不適合不可預測的任務 | 適合網路請求、計時器,不受時間阻塞的操作任務 |
常用方法/函數 | Array.forEach() 、Math.max() |
setTimeout() 、fetch() 、Promises 、async/await |
範例 | ||
結果 | 1->2->3 | 1->3->2 |
Promise
是 JavaScript 的內建對象,用來表示一個非同步操作的最終完成或失敗的結果值,主要有三種狀態:
Pending(待定):初始狀態,既不是成功也不是失敗。
Fulfilled(已兌現):操作成功完成。
Rejected(已拒絕):操作失敗。
創建 Promise
使用 Promise
建構函數可以建立一個新的 Promise
物件。
建構函式接受一個執行器函式作為參數,函式有兩個參數:resolve
和 reject
;當非同步操作成功時,呼叫 resolve
;當非同步操作失敗時,呼叫 reject
。
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
// 建構一個新的 Promise 函式
setTimeout(() => {
if (status) {
resolve("promiseSetTimeout 成功");
} else {
reject("promiseSetTimeout 失敗");
}
}, 0);
});
};
promiseSetTimeout(true).then(function (res) {
console.log(res); // promiseSetTimeout 成功
});
🔔 程式碼補充說明:
呼叫promiseSetTimeout
後把狀態參數帶入,然後運行promise
過程,接著進入pending
狀態,等到pending
狀態結束才會往成功或失敗前進。
使用 Promise
可以使用 then
和 catch
方法來處理 Promise
的結果或錯誤。
then
:用於處理 Promise 的成功結果。catch
:用來處理 Promise 的錯誤。
myPromise
.then((result) => {
console.log(result); // 成功
})
.catch((error) => {
console.log(error); // 失敗
});
Promise chain
Promise chain 是一種使用 .then()
和 .catch()
方法依序處理非同步操作的技術。
API 取得資料後,可能取得第一個後才能取第二個接著第三個,這時候就可以使用 promise
的方法,這有助於避免回調地獄,即嵌套回調變得難以閱讀和管理的情況。
圖片來源:Node 7.6 + Koa 2: asynchronous flow control made right
以下是用 Promise chain 處理多個連續的非同步操作,並且每個 .then
中的處理函數都可以處理前一個 .then
的結果。
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Data fetched"), 1000);
});
};
const processData = (data) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`${data}and processed`), 1000);
});
};
fetchData()
.then((result) => {
console.log(result); // Data fetched
return processData(result);
})
.then((processedResult) => {
console.log(processedResult); // Data fetched and processed
})
.catch((error) => {
console.error("Error::", error);
});
error handle
在 Promise 鏈中,錯誤處理可以透過 .catch()
捕捉鏈中任何 .then
方法拋出的錯誤,並且可以在鏈的最後進行統一處理。
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (status) {
resolve("promiseSetTimeout 成功");
} else {
reject("promiseSetTimeout 失敗");
}
}, 500);
});
};
promiseSetTimeout(true)
.then((res) => {
console.log(1, res);
return promiseSetTimeout(false);
})
.then((res) => {
console.log(2, res);
return promiseSetTimeout(true);
})
.then((res) => {
console.log(3, res);
})
.catch((err) => {
console.log(err);
return promiseSetTimeout(true);
})
.then((res) => {
console.log(`我回來啦~`, res);
});
依照以上程式碼回傳結果順序:1 'promiseSetTimeout 成功’ → promiseSetTimeout 失敗 → 我回來啦~ promiseSetTimeout 成功
因為,Promise 遇到錯誤的時候,就算中間有再多的串接,還是會找最近的 .catch
回傳,中間就直接跳過,如果後面又有串接就會執行。
Promise.all()
-> 實戰中使用率比較高,但也相對很雷!面試有被問過!Promise.all()
會同時執行,並且在全部完成
後統一回傳陣列,這個陣列的內容也是 promise 中 resolve
的內容。
不過,使用這個方法有個很大的致命傷:如果其中有事件 reject
,那麼此 promise 也均視為失敗進入 .catch
。
const promise1 = Promise.resolve(8);
const promise2 = new Promise((resolve, reject) =>
setTimeout(resolve, 100, "kuku")
);
const promise3 = 666;
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // [8,"kuku",666]
})
.catch((error) => {
console.error("Error:", error);
});
Promise.race()
-> 很少用到
使用 Promise.race
傳入多個 promise 事件,這個方法僅會回傳第一個完成的事件。
const promise1 = new Promise((resolve, reject) =>
setTimeout(resolve, 500, "我還要化妝需要 500 ms!")
);
const promise2 = new Promise((resolve, reject) =>
setTimeout(resolve, 100, "我很快,叫我 100ms 先生!")
);
Promise.race([promise1, promise2])
.then((value) => {
console.log(value); // "我很快,叫我 100ms 先生!"
})
.catch((error) => {
console.error("Error:", error);
});